package com.dragon.modules.yuegou.client.wechat_mini.security.impl;

import com.dragon.modules.yuegou.client.wechat_mini.client.AuthClient;
import com.dragon.modules.yuegou.client.wechat_mini.common.MiniConfig;
import com.dragon.modules.yuegou.client.wechat_mini.model.AccessToken;
import com.dragon.modules.yuegou.client.wechat_mini.response.TokenResponse;
import com.dragon.modules.yuegou.client.wechat_mini.security.AccessTokenRepository;
import com.dragon.modules.yuegou.client.wechat_mini.security.AccessTokenService;
import com.dragon.modules.yuegou.web.Check;

import java.time.Duration;

/**
 * 接口调用凭证服务的默认实现
 * 
 * @author liuwu
 * @date 2019年7月6日
 *
 */
public class AccessTokenServiceImpl implements AccessTokenService {

	/**
	 * 安全时间<br>
	 * 为了避免这种场景: 如果AccessToken在T时刻失效, 那么恰好在T时刻获取了AccessToken, 在(T + dt) 时刻使用,
	 * 发现已经失效了.<br>
	 * 所以, 如果AccessToken在T时刻失效, 为了保证使用的时候确实有效, 在(T -
	 * SAFE_TIME)时刻之后获取的AccessToken被认为是有风险的, 应当当作失效AccessToken处理.
	 */
	private static final Duration SAFE_TIME = Duration.ofMinutes(1);

	private final AuthClient client;

	private final AccessTokenRepository repository;

	private final MiniConfig config;

	public AccessTokenServiceImpl(AuthClient client, AccessTokenRepository repository, MiniConfig config) {
		super();
		Check.notNull(repository, "repository is required");
		Check.notNull(config, "config is required");
		this.client = client;
		this.repository = repository;
		this.config = config;
	}

	@Override
	public AccessToken getAccessToken() {
		AccessToken accessToken = getValidAccessTokenFromRepository();
		if (accessToken != null) {
			return accessToken;
		}
		return refreshAccessToken();
	}

	@Override
	public AccessToken refreshAccessToken() {
		AccessToken accessToken = getAccessTokenFromRemote();
		return repository.save(config.getAppid(), accessToken);
	}

	/**
	 * 远程获取有效的AccessToken
	 * 
	 * @return AccessToken
	 */
	protected AccessToken getAccessTokenFromRemote() {
		TokenResponse tokenResponse = client.getAccessToken(config.getAppid(), config.getSecret());
		tokenResponse.check();
		return new AccessToken(tokenResponse.getAccessToken(), tokenResponse.getExpiresIn());
	}

	/**
	 * 从本地仓库获取有效的AccessToken
	 * 
	 * @return AccessToken
	 */
	protected AccessToken getValidAccessTokenFromRepository() {
		AccessToken accessToken = repository.get(config.getAppid());
		if (accessToken == null) {
			return null;
		}
		if (accessToken.isExpiresAfter(SAFE_TIME)) {
			return null;
		}
		return accessToken;
	}

}
